SpringBoot整合Mybatis实现多数据源配置与跨数据源事务实例

您所在的位置:网站首页 springboot mybatis 配置 SpringBoot整合Mybatis实现多数据源配置与跨数据源事务实例

SpringBoot整合Mybatis实现多数据源配置与跨数据源事务实例

2024-07-16 12:45| 来源: 网络整理| 查看: 265

Pom依赖 org.springframework.boot spring-boot-starter-parent 2.2.2.RELEASE org.springframework.boot spring-boot-starter-jdbc mysql mysql-connector-java com.alibaba druid-spring-boot-starter 1.2.14 org.springframework.boot spring-boot-starter-test org.mybatis.spring.boot mybatis-spring-boot-starter 2.2.2 com.github.pagehelper pagehelper-spring-boot-starter 1.3.0 org.springframework.boot spring-boot-starter-aop application.yml

# 数据源 spring:   datasource:     # master主数据源配置     master:       driverClassName: com.mysql.cj.jdbc.Driver       url: jdbc:mysql://localhost:3306/ds1?characterEncoding=UTF-8&serverTimezone=GMT%2B8       username: root       password: root     # cluster从数据源配置     cluster:       driverClassName: com.mysql.cj.jdbc.Driver       url: jdbc:mysql://localhost:3306/ds2?characterEncoding=UTF-8&serverTimezone=GMT%2B8       username: root       password: root #pagehelper分页插件配置 pagehelper:   master:     helper-dialect: mysql  #设置数据库类型     reasonable: true  #开启合理化:页码=总页数查询最后一页     support-methods-arguments: true  #支持通过 Mapper 接口参数来传递分页参数     params: count=countsql

多数据源配置

Java代码方式创建主从数据源配置。 使用@Primary: 用于指定bean的注入优先级。被@Primary修饰的bean对象优先注入

MasterDataSourceConfig /** * @ClassName: MasterDataSourceConfig * @Description: mysql主库配置类 */ @Configuration @MapperScan(basePackages = MasterDataSourceConfig.PACKAGE,sqlSessionTemplateRef = "masterSqlSessionTemplate") public class MasterDataSourceConfig { /** * 设置扫描包的路径 */ public static final String PACKAGE = "cn.zysheep.dao.master"; /** * Mapper配置文件路径 */ public static final String MAPPER_LOCATION = "classpath*:mapper/master/*.xml"; /** * 实体别名 */ public static final String ENTITY_ALIASES = "cn.zysheep.entity"; @Value("${spring.datasource.master.driverClassName}") private String driverClassName; @Value("${spring.datasource.master.url}") private String url; @Value("${spring.datasource.master.username}") private String user; @Value("${spring.datasource.master.password}") private String password; @Value("${pagehelper.master.helper-dialect}") private String helperDialect; @Value("${pagehelper.master.reasonable}") private String reasonable; /** * 创建数据源 * @return DataSource * @Primary: 用于指定bean的注入优先级。被@Primary修饰的bean对象优先注入 */ @Bean(name = "masterDataSource") @Primary public DataSource masterDataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName(driverClassName); dataSource.setUrl(url); dataSource.setUsername(user); dataSource.setPassword(password); return dataSource; } /** * 创建工厂 *@param dataSource *@throws Exception *@return SqlSessionFactory */ @Bean(name = "masterSqlSessionFactory") @Primary public SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); sessionFactory.setDataSource(dataSource); sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATION)); sessionFactory.setTypeAliasesPackage(ENTITY_ALIASES); org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(); // 字段值为空也可以插入 configuration.setJdbcTypeForNull(JdbcType.NULL); configuration.setCallSettersOnNulls(true); // mybatis log configuration.setLogImpl(StdOutImpl.class); sessionFactory.setConfiguration(configuration); PageInterceptor pageInterceptor = new PageInterceptor(); // 分页配置 Properties p = new Properties(); p.setProperty("helperDialect", helperDialect); p.setProperty("reasonable", reasonable); pageInterceptor.setProperties(p); sessionFactory.setPlugins(pageInterceptor); return sessionFactory.getObject(); } /** * 创建事务 *@param dataSource *@return DataSourceTransactionManager */ @Bean(name = "masterTransactionManager") @Primary public DataSourceTransactionManager masterDataSourceTransactionManager(@Qualifier("masterDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } /** * 创建模板 *@param sqlSessionFactory *@return SqlSessionTemplate */ @Bean(name = "masterSqlSessionTemplate") @Primary public SqlSessionTemplate masterSqlSessionTemplate(@Qualifier("masterSqlSessionFactory") SqlSessionFactory sqlSessionFactory) { return new SqlSessionTemplate(sqlSessionFactory); } } ClusterDataSourceConfig /** * @ClassName: DataSourceConfig * @Description: mysql从库配置类 */ @Configuration @MapperScan(basePackages = ClusterDataSourceConfig.PACKAGE,sqlSessionTemplateRef = "clusterSqlSessionTemplate") public class ClusterDataSourceConfig { /** * 设置扫描包的路径 */ public static final String PACKAGE = "cn.zysheep.dao.cluster"; /** * Mapper配置文件路径 */ public static final String MAPPER_LOCATION = "classpath*:mapper/cluster/*.xml"; /** * 实体别名 */ public static final String ENTITY_ALIASES = "cn.zysheep.entity"; @Value("${spring.datasource.cluster.driverClassName}") private String driverClassName; @Value("${spring.datasource.cluster.url}") private String url; @Value("${spring.datasource.cluster.username}") private String user; @Value("${spring.datasource.cluster.password}") private String password; @Value("${pagehelper.master.helper-dialect}") private String helperDialect; @Value("${pagehelper.master.reasonable}") private String reasonable; /** * 创建数据源 *@return DataSource */ @Bean(name = "clusterDataSource") public DataSource masterDataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName(driverClassName); dataSource.setUrl(url); dataSource.setUsername(user); dataSource.setPassword(password); return dataSource; } /** * 创建工厂 *@param dataSource *@throws Exception *@return SqlSessionFactory */ @Bean(name = "clusterSqlSessionFactory") public SqlSessionFactory masterSqlSessionFactory(@Qualifier("clusterDataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); sessionFactory.setDataSource(dataSource); sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATION)); sessionFactory.setTypeAliasesPackage(ENTITY_ALIASES); org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(); // 字段值为空也可以插入 configuration.setJdbcTypeForNull(JdbcType.NULL); configuration.setCallSettersOnNulls(true); // mybatis log configuration.setLogImpl(StdOutImpl.class); sessionFactory.setConfiguration(configuration); PageInterceptor pageInterceptor = new PageInterceptor(); // 分页配置 Properties p = new Properties(); p.setProperty("helperDialect", helperDialect); p.setProperty("reasonable", reasonable); pageInterceptor.setProperties(p); sessionFactory.setPlugins(pageInterceptor); return sessionFactory.getObject(); } /** * 创建事务管理器 *@param dataSource *@return DataSourceTransactionManager */ @Bean(name = "clusterTransactionManager") public DataSourceTransactionManager masterDataSourceTransactionManager(@Qualifier("clusterDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } /** * 创建模板 *@param sqlSessionFactory *@return SqlSessionTemplate */ @Bean(name = "clusterSqlSessionTemplate") public SqlSessionTemplate masterSqlSessionTemplate(@Qualifier("clusterSqlSessionFactory") SqlSessionFactory sqlSessionFactory) { return new SqlSessionTemplate(sqlSessionFactory); } } 启动类

启动类添加@EnableTransactionManagement注解,开启注解事务

@EnableTransactionManagement @SpringBootApplication(exclude = PageHelperAutoConfiguration.class) public class MultiDataSourceApplication { public static void main(String[] args) { SpringApplication.run(MultiDataSourceApplication.class, args); } } 使用

在service层类或者方法上添加@Transactional注解。并指定属性 事务管理器transactionManager的值,根据使用不同的数据源,选择不同的数据事务管理器

@Transactional(transactionManager = "masterTransactionManager") public void insertUser() { // TODO 添加用户业务操作 } @Transactional(transactionManager = "clusterTransactionManager") public void listUser() { // TODO 查询用户业务操作 }

思考: @Transactional只能指定一个事务管理器,并且注解不允许重复,所以就只能使用一个数据源的事务管理器了。那么对于一个方法涉及到多个数据源操作需要保证事务一致性的怎么办呢?

实现跨数据源事务 基于注解+AOP使用编程式事务的方式实现跨数据源事务 @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface MultiDataSourceTransactional { /** * 事务管理器数组 */ String[] transactionManagers(); }

首先多个数据源的事务分别都开起来,然后各事务分别去执行对应的sql(此所谓第一阶段提交),最后如果都成功就把事务全部提交,只要有一个失败就把事务都回滚——此所谓第二阶段提交。

/** * 需要注意的点: * 1)声明事务和提交事务或者回滚事务的顺序应该相反的,就是先进后出,所以采用了栈来存储。 * 2)线程执行结束后记得清空本地变量。 * 3)可以参照@Transactional的那些属性升级功能,比如隔离级别回滚异常等。 */ @Component @Aspect public class MultiTransactionAop { /** * 线程本地变量:为什么使用栈?※为了达到后进先出的效果※ */ private static final ThreadLocal THREAD_LOCAL = new ThreadLocal(); private static Logger logger = LoggerFactory.getLogger(MultiTransactionAop.class); /** * 用于获取事务管理器 */ @Autowired private ApplicationContext applicationContext; /** * 事务声明 */ private DefaultTransactionDefinition def = new DefaultTransactionDefinition(); { // 非只读模式 def.setReadOnly(false); // 事务隔离级别:采用数据库的 def.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT); // 事务传播行为 def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); } /** * 切点 */ @Pointcut("@annotation(cn.zysheep.datasource.anntation.MultiDataSourceTransactional)") public void pointcut() { } /** * 声明事务 * * @param transactional 注解 */ @Before("pointcut() && @annotation(transactional)") public void before(MultiDataSourceTransactional transactional) { // 根据设置的事务名称按顺序声明,并放到ThreadLocal里 String[] transactionManagerNames = transactional.transactionManagers(); Stack pairStack = new Stack(); for (String transactionManagerName : transactionManagerNames) { DataSourceTransactionManager transactionManager = applicationContext.getBean(transactionManagerName, DataSourceTransactionManager.class); TransactionStatus transactionStatus = transactionManager.getTransaction(def); Map transactionMap = new HashMap(); transactionMap.put(transactionManager, transactionStatus); pairStack.push(transactionMap); } THREAD_LOCAL.set(pairStack); } /** * 后置增强,相当于AfterReturningAdvice,方法退出时执行 * * 提交事务 */ @AfterReturning("pointcut()") public void afterReturning() { // ※栈顶弹出(后进先出) Stack pairStack = THREAD_LOCAL.get(); while (!pairStack.empty()) { Map pair = pairStack.pop(); pair.forEach((key,value)->key.commit(value)); } THREAD_LOCAL.remove(); } /** * 异常抛出增强,相当于ThrowsAdvice * * 回滚事务 */ @AfterThrowing(value = "pointcut()") public void afterThrowing() { // ※栈顶弹出(后进先出) Stack pairStack = THREAD_LOCAL.get(); logger.info("========================="); logger.info("Pair Stack:{}", pairStack); logger.info("========================="); while (!pairStack.empty()) { Map pair = pairStack.pop(); pair.forEach((key,value)->key.rollback(value)); } THREAD_LOCAL.remove(); } }

在事务的方法上添加前面我们的多数据源事务注解

/** * 测试多数据源事务 */ @MultiDataSourceTransactional(transactionManagers = {"masterTransactionManager", "clusterTransactionManager"}) @Override public void saveUser() { String rid = UUID.randomUUID().toString(); User user = new User(); user.setId(rid); userMapper.saveUser(user); Log log = new Log(); log.setId(rid); logMapper.saveLog(log); throw new RuntimeException(); }

到此这篇关于SpringBoot整合Mybatis实现多数据源配置与跨数据源事务实例的文章就介绍到这了。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3